package br.unb.cic.lp.gol;

import java.util.ArrayList;
import java.util.List;

public abstract class CommonConwayHighlife implements Estrategia {

    protected int width, height;
    protected Cell[][] cells;

    public CommonConwayHighlife(int height, int width, Cell[][] cells) {
        this.height = height;
        this.width = width;
        this.cells = cells;
    }

    /**
     * Calcula uma nova geracao do ambiente. Essa implementacao utiliza o
     * algoritmo do Conway, ou seja:
     * 
     * a) uma celula morta com exatamente tres celulas vizinhas vivas se torna
     * uma celula viva.
     * 
     * b) uma celula viva com duas ou tres celulas vizinhas vivas permanece
     * viva.
     * 
     * c) em todos os outros casos a celula morre ou continua morta.
     */
    public void nextGeneration() {
        List<Cell> mustRevive = new ArrayList<Cell>();
        List<Cell> mustKill = new ArrayList<Cell>();
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                if (shouldRevive(i, j)) {
                    mustRevive.add(cells[i][j]);
                } else if ((!shouldKeepAlive(i, j)) && cells[i][j].isAlive()) {
                    mustKill.add(cells[i][j]);
                }
            }
        }

        for (Cell cell : mustRevive) {
            cell.revive();
            // Comentado devido ao observer
            // statistics.recordRevive();
        }

        for (Cell cell : mustKill) {
            cell.kill();
            // Comentado devido ao observer
            // statistics.recordKill();
        }
    }

    /* verifica se uma celula deve ser mantida viva */
    private boolean shouldKeepAlive(int i, int j) {
        return (cells[i][j].isAlive())
                && (numberOfNeighborhoodAliveCells(i, j) == 2 || numberOfNeighborhoodAliveCells(i, j) == 3);
    }

    /* verifica se uma celula deve (re)nascer */
    protected abstract boolean shouldRevive(int i, int j);

    /*
     * Computa o numero de celulas vizinhas vivas, dada uma posicao no ambiente
     * de referencia identificada pelos argumentos (i,j).
     */
    protected int numberOfNeighborhoodAliveCells(int i, int j) {
        int alive = 0;
        for (int a = i - 1; a <= i + 1; a++) {
            for (int b = j - 1; b <= j + 1; b++) {
                if (validPosition(a, b) && (!(a == i && b == j)) && cells[a][b].isAlive()) {
                    alive++;
                }
            }
        }
        return alive;
    }

    /*
     * Verifica se uma posicao (a, b) referencia uma celula valida no tabuleiro.
     */
    private boolean validPosition(int a, int b) {
        return a >= 0 && a < height && b >= 0 && b < width;
    }

}
